﻿using System;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace PdfSdk
{
    public abstract class ObjectWrapper : IObjectWrapper
    {
        private static readonly BindingFlags s_bindingAttr = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
        private readonly BindingFlags _bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
        protected readonly object _originalObject;
        protected readonly Type _originalType;

        public ObjectWrapper(object obj)
        {
            _originalObject = obj ?? throw new ArgumentNullException(nameof(obj));
            _originalType = obj.GetType();
        }

        public ObjectWrapper(object obj, Type type)
        {
            _originalObject = obj ?? throw new ArgumentNullException(nameof(obj));
            _originalType = type ?? throw new ArgumentNullException(nameof(type));
        }

        public object OriginalObject => _originalObject;

        public override int GetHashCode() => _originalObject.GetHashCode();

        public override bool Equals(object obj)
        {
            if (obj is not ObjectWrapper wrapper)
                return false;
            return _originalObject.Equals(wrapper._originalObject);
        }

        protected object GetFieldValue([CallerMemberName] string name = "")
        {
            return _originalType.GetField(name, _bindingAttr)?.GetValue(_originalObject);
        }

        internal static object GetFieldValue(Type originalType, [CallerMemberName] string name = "")
        {
            return originalType.GetField(name, s_bindingAttr)?.GetValue(null);
        }

        protected void SetFieldValue(object value, [CallerMemberName] string name = "")
        {
            if (value is ObjectWrapper wrapper)
                value = wrapper._originalObject;
            _originalType.GetField(name, _bindingAttr)?.SetValue(_originalObject, value);
        }

        protected object GetPropertyValue([CallerMemberName] string name = "")
        {
            return _originalType.GetProperty(name, _bindingAttr)?.GetValue(_originalObject);
        }

        internal static object GetPropertyValue(Type originalType, [CallerMemberName] string name = "")
        {
            return originalType.GetProperty(name, s_bindingAttr)?.GetValue(null);
        }

        protected void SetPropertyValue(object value, [CallerMemberName] string name = "")
        {
            if (value is ObjectWrapper wrapper)
                value = wrapper._originalObject;
            _originalType.GetProperty(name, _bindingAttr)?.SetValue(_originalObject, value);
        }

        internal static void SetPropertyValue(Type originalType, object value, [CallerMemberName] string name = "")
        {
            originalType.GetProperty(name, s_bindingAttr)?.SetValue(null, value);
        }

        protected object InvokeMethod([CallerMemberName] string name = "")
        {
            return _originalType.GetMethod(name, _bindingAttr)?.Invoke(_originalObject, null);
        }

        protected object InvokeMethod(string name, params object[] parameters)
        {
            return _originalType.GetMethod(name, _bindingAttr)?.Invoke(_originalObject, parameters);
        }

        protected object InvokeMethod(string name, Type[] types, params object[] parameters)
        {
            return _originalType.GetMethod(name, _bindingAttr, null, types, null)?.Invoke(_originalObject, parameters);
        }

        internal static object InvokeMethod(Type originalType, [CallerMemberName] string name = "")
        {
            return originalType.GetMethod(name, s_bindingAttr)?.Invoke(null, null);
        }

        internal static object InvokeMethod(Type originalType, string name, params object[] parameters)
        {
            return originalType.GetMethod(name, s_bindingAttr)?.Invoke(null, parameters);
        }

        internal static object CreateInstance(Type originalType, params object[] args)
        {
            return Activator.CreateInstance(
                originalType,
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance,
                null,
                args,
                null,
                null);
        }
    }
}
